package com.tuanzi.loan.core.repository;

import java.util.*;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.jooq.*;
import org.jooq.impl.DSL;
import org.jooq.impl.TableImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import com.google.common.collect.Lists;
import com.tuanzi.loan.core.exception.SystemException;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

/**
 * @author Ray 2017/09/21
 */
@Slf4j
@SuppressWarnings({ "rawtypes", "unchecked" })
public abstract class JOOQBaseRepository {

	@Data
	private class TableJoinCondition {
		private TableLike tableLike;
		private Condition condition;
		private JoinType joinType;
	}

	@Autowired
	protected DSLContext dslContext;

	private static final ThreadLocal<List<Condition>> CONDITIONS = new ThreadLocal<List<Condition>>();
	private static final ThreadLocal<List<SortField>> SORTS = new ThreadLocal<List<SortField>>();
	private static final ThreadLocal<List<SelectField>> FIELDS = new ThreadLocal<List<SelectField>>();
	private static final ThreadLocal<List<TableJoinCondition>> JOIN = new ThreadLocal<>();

	protected abstract TableImpl getTable();

	public JOOQBaseRepository eq(TableField tableField, Object value) {
		if (checkValueIsNull(value)) {
			getCurrentConditions().add(tableField.eq(value));
		}
		return this;
	}

	public JOOQBaseRepository neq(TableField tableField, Object value) {
		if (checkValueIsNull(value)) {
			getCurrentConditions().add(tableField.notEqual((value)));
		}
		return this;
	}

	public JOOQBaseRepository contains(TableField tableField, String value) {
		if (checkValueIsNull(value)) {
			getCurrentConditions().add(tableField.contains(value));
		}
		return this;
	}

	public JOOQBaseRepository contains(TableField tableField, Integer value) {
		if (checkValueIsNull(value)) {
			getCurrentConditions().add(tableField.contains(value));
		}
		return this;
	}


	public JOOQBaseRepository startsWith(TableField tableField, String value) {
		if (checkValueIsNull(value)) {
			getCurrentConditions().add(tableField.startsWith(value));
		}
		return this;
	}

	public JOOQBaseRepository endsWith(TableField tableField, String value) {
		if (checkValueIsNull(value)) {
			getCurrentConditions().add(tableField.endsWith(value));
		}
		return this;
	}

	public JOOQBaseRepository in(TableField tableField, Collection<?> values) {
		if (CollectionUtils.isNotEmpty(values)) {
			getCurrentConditions().add(tableField.in(values));
		}
		return this;
	}

	public JOOQBaseRepository lt(TableField tableField, Object value) {
		if (checkValueIsNull(value)) {
			getCurrentConditions().add(tableField.lt(value));
		}
		return this;
	}

	public JOOQBaseRepository le(TableField tableField, Object value) {
		if (checkValueIsNull(value)) {
			getCurrentConditions().add(tableField.le(value));
		}
		return this;
	}

	public JOOQBaseRepository gt(TableField tableField, Object value) {
		if (checkValueIsNull(value)) {
			getCurrentConditions().add(tableField.gt(value));
		}
		return this;
	}

	public JOOQBaseRepository ge(TableField tableField, Object value) {
		if (checkValueIsNull(value)) {
			getCurrentConditions().add(tableField.ge(value));
		}
		return this;
	}

	public JOOQBaseRepository between(TableField tableField, Object value1, Object value2) {
		if (checkValueIsNull(value1) && checkValueIsNull(value2)) {
			getCurrentConditions().add(tableField.between(value1, value2));
		}
		return this;
	}

	public JOOQBaseRepository notIn(TableField tableField, List<String> values) {
		if (CollectionUtils.isNotEmpty(values)) {
			getCurrentConditions().add(tableField.notIn(values));
		}
		return this;
	}

	public JOOQBaseRepository isNull(TableField tableField) {
		getCurrentConditions().add(tableField.isNull());
		return this;
	}

	public JOOQBaseRepository isNotNull(TableField tableField) {
		getCurrentConditions().add(tableField.isNotNull());
		return this;
	}

	public JOOQBaseRepository orderBy(TableField tableField, Sort.Direction orderType) {
		getCurrentSorts().add(orderType == Sort.Direction.DESC ? tableField.desc() : tableField.asc());
		return this;
	}

	public JOOQBaseRepository innerJoin(TableLike table, Condition condition) {
		return join(table, condition, JoinType.JOIN);
	}

	private List<TableJoinCondition> getCurrentJoin() {
		List<TableJoinCondition> join = JOIN.get();
		if (join == null) {
			join = Lists.newArrayList();
			JOIN.set(join);
		}
		return join;
	}

	private JOOQBaseRepository join(TableLike table, Condition condition, JoinType joinType) {
		TableJoinCondition tableJoinCondition = new TableJoinCondition();
		tableJoinCondition.setTableLike(table);
		tableJoinCondition.setCondition(condition);
		tableJoinCondition.setJoinType(joinType);
		getCurrentJoin().add(tableJoinCondition);
		return this;
	}

	public JOOQBaseRepository leftJoin(TableLike table, Condition condition) {
		return join(table, condition, JoinType.LEFT_OUTER_JOIN);
	}

	public JOOQBaseRepository fields(SelectField... properties) {
		if (properties != null && properties.length > 0) {
			getCurrentFields().addAll(Arrays.asList(properties));
		}
		return this;
	}

	public <R extends Record, E> List<E> search(RecordMapper<R, E> recordMapper) {
		return this.search(0, Integer.MAX_VALUE).map(recordMapper);
	}

	public <R extends Record, E> Page<E> search(Pageable pageable, RecordMapper<R, E> recordMapper) {
		addSorts(pageable.getSort());
		Long count = this.structureCountSelectQuery().fetchOne().into(Long.class);
		List<R> content = this.search(pageable.getOffset(), pageable.getPageSize()).map(recordMapper);
		return new PageImpl(content, pageable, count);
	}

	public List<Record> search() {
		return this.search(0, Integer.MAX_VALUE);
	}

	public Long count() {
		try {
			Long count = this.structureCountSelectQuery().fetchOne().into(Long.class);
			return count;
		} finally {
			destroy();
		}
	}

	public <T> List<T> search(Class<T> cls) {
		return this.search(0, Integer.MAX_VALUE).into(cls);
	}

	public Result search(int offset, int limit) {
		try {
			SelectQuery selectQuery = structureFieldSelectQuery();
			selectQuery.addOffset(offset);
			selectQuery.addLimit(limit);
			log.info("JOOQBaseRepository generate SQL : {}", selectQuery.getSQL());
			return selectQuery.fetch();
		} finally {
			destroy();
		}
	}

	public Record searchOne() {
		try {
			SelectQuery selectQuery = structureFieldSelectQuery();
			log.info("JOOQBaseRepository generate SQL : {}", selectQuery.getSQL());
			return selectQuery.fetchOne();
		} finally {
			destroy();
		}
	}

	public Page<Record> search(Pageable pageable) {
		addSorts(pageable.getSort());
		Long count = this.structureCountSelectQuery().fetchOne().into(Long.class);
		List<Record> content = this.search(pageable.getOffset(), pageable.getPageSize());
		return new PageImpl(content, pageable, count);
	}

	private SelectQuery structureFieldSelectQuery() {
		SelectQuery selectQuery = structureSelectQuery();
		selectQuery.addSelect(getCurrentFields());
		return selectQuery;
	}

	private SelectQuery addJoin(SelectQuery selectQuery, Map<TableLike, Condition> join, JoinType joinType) {
		for (Map.Entry<TableLike, Condition> entry : join.entrySet()) {
			selectQuery.addJoin(entry.getKey(), joinType, entry.getValue());
		}
		return selectQuery;
	}

	@SuppressWarnings("deprecation")
	private SelectQuery structureSelectQuery() {
		SelectQuery selectQuery = dslContext.selectQuery(getTable());
		List<TableJoinCondition> tableJoinConditionList = getCurrentJoin();
		for (TableJoinCondition tableJoinCondition : tableJoinConditionList) {
			selectQuery.addJoin(tableJoinCondition.getTableLike(), tableJoinCondition.getJoinType(), tableJoinCondition.getCondition());
		}
		selectQuery.addConditions(getCurrentConditions());
		selectQuery.addOrderBy(getCurrentSorts());
		return selectQuery;
	}

	private SelectQuery structureCountSelectQuery() {
		SelectQuery selectQuery = structureSelectQuery();
		selectQuery.addSelect(DSL.count());
		return selectQuery;
	}

	private void addSorts(Sort sort) {
		if (sort == null) {
			return;
		}
		Iterator<Sort.Order> iterator = sort.iterator();
		while (iterator.hasNext()) {
			Sort.Order order = iterator.next();
			orderBy(getTableField(order.getProperty()), order.getDirection());
		}
	}

	private TableField getTableField(String fieldName) {
		try {
			return (TableField) getTable().getClass().getDeclaredField(fieldName).get(getTable());
		} catch (Exception e) {
			throw new SystemException("property not found " + fieldName);
		}
	}

	private List<Condition> getCurrentConditions() {
		List<Condition> wheres = CONDITIONS.get();
		if (wheres == null) {
			wheres = Lists.newArrayList();
			CONDITIONS.set(wheres);
		}
		return wheres;
	}

	private List<SortField> getCurrentSorts() {
		List<SortField> sorts = SORTS.get();
		if (sorts == null) {
			sorts = Lists.newArrayList();
			SORTS.set(sorts);
		}
		return sorts;
	}

	protected List<SelectField> getCurrentFields() {
		List<SelectField> properties = FIELDS.get();
		if (properties == null) {
			properties = Lists.newArrayList();
			FIELDS.set(properties);
		}
		return properties;
	}



	private void destroy() {
		CONDITIONS.remove();
		SORTS.remove();
		FIELDS.remove();
		JOIN.remove();
	}

	private boolean checkValueIsNull(Object value) {
		if (value == null) {
			return false;
		}

		if (value instanceof String) {
			return StringUtils.isNotBlank(String.valueOf(value));
		}

		return true;
	}
}
